﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Data;
using System.Windows.Input;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Regions;
using Tanmia.Basis.Concretes;
using Tanmia.Basis.Core;
using Tanmia.Basis.Data;
using Tanmia.Basis.Domain;
using Tanmia.Basis.Prism;
using Tanmia.Basis.Properties;
using Tanmia.Basis.Security;
using Tanmia.Basis.ViewInterface;

namespace Tanmia.Basis.ViewModel
{
    /// <summary>
    /// Represent the base class for all ViewModel of the Views.
    /// </summary>
    /// <typeparam name="T">BusinessObject which is going to be presented through this ViewModel.</typeparam>
    public abstract class BaseEditAndBrowseViewModel<T> : BaseViewModel, IDataErrorInfo, IDisposable
           where T : BaseBusinessObject, new()
    {
        public ViewInfo BrowserInfo { get; protected set; }

        private T entity;
        private readonly BackgroundWorker refreshBackgroundWorker;

        protected BusinessDomain<T> DomainObject { get; set; }
        protected IMessageService MessageService { get; private set; }
        protected IRegionManager RegionManager { get; private set; }

        /// <summary>
        /// Gets a command which represent refreshing of the BrowserView.
        /// </summary>
        public ICommand RefreshCommand { get; protected set; }
        public ICommand DeleteCommand { get; protected set; }
        public ICommand SaveCommand { get; protected set; }
        public ICommand SaveAndNewCommand { get; protected set; }
        public ICommand SaveAndCloseCommand { get; protected set; }
        public ICommand CloseEditorCommand { get; protected set; }

        /// <summary>
        /// Gets human readable name of the business entity.
        /// </summary>
        public string EntityName { get; protected set; }

        /// <summary>
        /// Gets or Sets the business entity which is currently manipulated by Editor Window.
        /// </summary>
        public T Entity
        {
            get { return entity; }
            set
            {
                if (entity != null)
                    entity.PropertyChanged -= Entity_PropertyChanged;

                entity = value;
                entity.PropertyChanged += Entity_PropertyChanged;
                RaisePropertyChanged("Entity");
            }
        }

        /// <summary>
        /// Gets or sets a CollectionView of all entities which will be showed in the browser view.
        /// </summary>
        public ICollectionView Entities { get; set; }

        /// <summary>
        /// Gets the selected entity on the Browser Window.
        /// </summary>
        public T CurrentEntity
        {
            get { return Entities.CurrentItem as T; }
        }

        public BaseEditAndBrowseViewModel(IShellView shellView, IRegionManager regionManager, IMessageService messageService, BusinessDomain<T> domainObject)
            : base(shellView)
        {
            this.RegionManager = regionManager;
            this.MessageService = messageService;
            this.DomainObject = domainObject;

            refreshBackgroundWorker = new BackgroundWorker();
            //refreshBackgroundWorker.DoWork += (sender, args) => PerformActualRefresh();
            refreshBackgroundWorker.DoWork += new DoWorkEventHandler(refreshBackgroundWorker_DoWork);
            refreshBackgroundWorker.RunWorkerCompleted += (sender, args) => CompleteRefresh();

            SaveCommand = new DelegateCommand(OnSave, OnCanSave);
            SaveAndNewCommand = new DelegateCommand(OnSaveAndNew, OnCanSave);
            SaveAndCloseCommand = new DelegateCommand(OnSaveAndClose, OnCanSave);
            RefreshCommand = new DelegateCommand(OnRefresh);
            DeleteCommand = new DelegateCommand(OnDelete, OnCanDelete);
            CloseEditorCommand = new DelegateCommand(OnEditorClose);
        }

        void refreshBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            PerformActualRefresh();
        }

        private void Entity_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            ((DelegateCommand)SaveCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)SaveAndNewCommand).RaiseCanExecuteChanged();
            ((DelegateCommand)SaveAndCloseCommand).RaiseCanExecuteChanged();
        }

        protected virtual void CompleteRefresh()
        {
            ShellView.UpdateStatus(string.Format(CultureInfo.CurrentCulture, Resources.Ready));
        }

        protected virtual void PerformActualRefresh()
        {
            Entities = CollectionViewSource.GetDefaultView(DomainObject.Gets());
            RaisePropertyChanged(() => Entities);
        }

        protected virtual void OnRefresh()
        {
            ShellView.UpdateStatus(string.Format(CultureInfo.CurrentCulture, Resources.RefreshBrowser, EntityName));
            refreshBackgroundWorker.RunWorkerAsync();
        }

        protected virtual void OnDelete()
        {

        }

        protected virtual bool OnCanDelete()
        {
            return true;
        }

        protected virtual bool OnCanSave()
        {
            return Entity.IsValid();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <exception cref="SaryaAccounting.Basis.ConstraintViolationException">Whether a constraint violation has happened in the database.</exception>
        /// <remarks>
        /// Steps of flow:
        ///     1. Validate
        ///     2. Save
        ///     3. RefreshBrowser
        ///     4. Return the result
        /// Handled Exception:
        ///     1. AccessDeniedSecurityException which is raised from the Domain Layer of the application.
        ///     2. ConstraintViolationException which is raised from the Database according to the constraint violation value.
        /// </remarks>
        protected virtual bool SaveCore()
        {
            bool retVal = false;

            try
            {
                Entity.Validate();
                if (Entity.IsValid())
                {
                    DomainObject.Save(Entity);
                    OnRefresh();
                    retVal = true;
                }
                else
                    retVal = false;
            }
            catch (ConstraintViolationException constVioEx)
            {
                MessageService.ShowExclamationMessage(constVioEx, "Duplicate value", true);
            }
            catch (AccessDeniedSecurityException secEx)
            {
                MessageService.ShowSecurityMessage(secEx, true);
            }

            return retVal;
        }

        protected virtual bool DeleteCore()
        {
            bool retVal = false;

            try
            {
                DomainObject.Delete(CurrentEntity);
                OnRefresh();
                retVal = true;
            }
            catch (AccessDeniedSecurityException secEx)
            {
                MessageService.ShowSecurityMessage(secEx, true);
            }

            return retVal;
        }

        protected virtual void OnSave()
        {
            using (new WaitingCursor(ShellView))
            {
                bool savingResult = SaveCore();
                UpdateStatus(savingResult, Resources.SaveSucceed, Resources.SaveUnsucceed);
            }
        }

        protected virtual void OnSaveAndNew()
        {
            using (new WaitingCursor(ShellView))
            {
                bool savingResult = SaveCore();

                if (savingResult)
                    Entity = new T();

                UpdateStatus(savingResult, Resources.SaveAndNewSucceed, Resources.SaveUnsucceed);
            }
        }

        protected virtual void OnSaveAndClose()
        {
            using (new WaitingCursor(ShellView))
            {
                bool savingResult = SaveCore();

                if (savingResult)
                {
                    Entity = new T();
                    OnEditorClose();
                }
                UpdateStatus(savingResult, Resources.SaveAndCloseSucceed, Resources.SaveUnsucceed);
            }
        }

        protected virtual void OnEditorClose()
        {
            IRegion windowRegion = RegionManager.Regions[ViewInfo.RegionName];
            object view = windowRegion.GetView(ViewInfo.ViewName);
            windowRegion.Remove(view);
        }

        protected void UpdateStatus(bool result, string succeedResource, string unsucceedResource)
        {
            if (result)
                ShellView.UpdateStatus(string.Format(CultureInfo.CurrentCulture, succeedResource, EntityName));
            else
                ShellView.UpdateStatus(string.Format(CultureInfo.CurrentCulture, unsucceedResource, EntityName));
        }

        #region IDataErrorInfo Members

        string IDataErrorInfo.Error
        {
            get { return Entity.Error; }
        }

        string IDataErrorInfo.this[string columnName]
        {
            get { return Entity[columnName]; }
        }

        #endregion

        #region IDisposable Members

        public virtual void Dispose()
        {
            refreshBackgroundWorker.Dispose();
        }

        #endregion

        public override void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback)
        {
            if (Entity.IsStateChanged)
                if (MessageService.RequestUserConfirmation("هل تريد حفظ التعديلات؟", "تأكيد إغلاق") == MessageServiceResultEnum.No)
                    continuationCallback(false);
        }

        public override void OnNavigatedTo(NavigationContext navigationContext)
        {
            Entity = navigationContext.GetParameter<T>(Constants.EntityParam);
            if (Entity == null)
                throw new ArgumentNullException(Constants.EntityParam);

            RaisePropertyChanged(() => Entity);

            base.OnNavigatedTo(navigationContext);
        }
    }
}
